package com.cityeye.dao;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import com.cityeye.search.SearchCriteria;
import com.cityeye.search.SearchExpr;
import com.cityeye.util.IEntity;

public abstract class AbstractEntityDao<T extends IEntity> {

	protected static SessionFactory sf;

	static {
		try {
			sf = new Configuration().configure().buildSessionFactory();
		} catch (HibernateException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public SessionFactory getSessionFactory() {
		return sf;
	}

	public SessionFactory setSessionFactory(SessionFactory sfac) {
		return sf = sfac;
	}

	/**
	 * Add a new entity to the collection of entities
	 */
	public void add(T entity) {
		sf.getCurrentSession().beginTransaction();
		doAdd(entity);
		sf.getCurrentSession().getTransaction().commit();
		// notifyAdd(entity);
	}
	
	/**
	 * Add a new entity to the collection of entities
	 */
	public void add(List<T> entities) {
		sf.getCurrentSession().beginTransaction();
		doAdd(entities);
		sf.getCurrentSession().getTransaction().commit();
		// notifyAdd(entity);
	}	

	/**
	 * Save the entity into database, and notify listener.
	 * 
	 * @param entity
	 */
	public void update(T entity) {
		sf.getCurrentSession().beginTransaction();
		doUpdate(entity);
		sf.getCurrentSession().getTransaction().commit();
		// notifyUpdate(entity);
	}

	/**
	 * @param entity
	 */
	public void remove(T entity) {
		sf.getCurrentSession().beginTransaction();
		doRemove(entity);
		sf.getCurrentSession().getTransaction().commit();
		// notifyRemove(entity);
	}

	public abstract T createNewEmptyEntity();

	public List<T> find(SearchExpr expr) {
		String key = expr.getSearchCriteria().getKey();
		Object value = expr.getExpectedValue();
		return this.findByProperty(getEntityClass(), key, value);
	};

	public void doAdd(List<T> entities) {
		for(T entity:entities){
			sf.getCurrentSession().save(entity);
		}
	}
	
	public void doAdd(T entity) {
		sf.getCurrentSession().save(entity);
	}

	public void doUpdate(T entity) {
		sf.getCurrentSession().update(entity);
	}

	public void doRemove(T entity) {
		sf.getCurrentSession().delete(entity);
	}

	public abstract SearchCriteria[] getSearchCriteria();

	public static IEntity findById(Class theclass, int id) {
		IEntity instance = null;
		try {
			sf.getCurrentSession().beginTransaction();
			instance = (IEntity) sf.getCurrentSession().get(theclass, id);
		} catch (RuntimeException re) {
			re.printStackTrace();
		} finally {
			sf.getCurrentSession().getTransaction().commit();
		}
		return instance;
	}

	public T findById(int id) {
		T instance = null;
		try {
			sf.getCurrentSession().beginTransaction();
			instance = (T) sf.getCurrentSession().get(getEntityClass(), id);
		} catch (RuntimeException re) {
			re.printStackTrace();
		} finally {
			sf.getCurrentSession().getTransaction().commit();
		}
		return instance;
	}

	public List<T> findByProperty(String prop, Object value) {
		return findByProperty(getEntityClass(),prop,value);
	}
	
	public <T extends IEntity> List<T> findByProperty(Class<T> entityClass,
			String prop, Object value) {
		List<T> instances = null;
		try {
			sf.getCurrentSession().beginTransaction();

			instances = sf.getCurrentSession().createCriteria(entityClass)
					.add(Restrictions.eq(prop, value)).list();
			return instances;
		} catch (RuntimeException re) {
			// Log.debug(re);
			re.printStackTrace();
		} finally {
			sf.getCurrentSession().getTransaction().commit();
		}
		return instances;
	}

	public <T extends IEntity> List<T> findByProperties(Class<T> entityClass,
			String[] props, Object[] values) {
		// Log.debug("getting Entity instance with " + props + "=" + values);
		sf.getCurrentSession().beginTransaction();

		Criteria crt = getSessionFactory().getCurrentSession().createCriteria(
				entityClass);

		for (int i = 0; i < props.length; i++) {
			if (values[i] instanceof Object[]) {
				Object[] range = (Object[]) values[i];
				crt = crt.add(Restrictions
						.between(props[i], range[0], range[1]));
			} else
				crt = crt.add(Restrictions.eq(props[i], values[i]));
		}

		@SuppressWarnings("unchecked")
		List<T> instances = crt.list();

		sf.getCurrentSession().getTransaction().commit();
		if (instances == null) {
			instances = Collections.EMPTY_LIST;
			// Log.debug("get successful, no instance found");
		} else {
			// Log.debug("get successful, instance found");
		}
		return instances;
	}

	public T loadFirst() {
		List<T> all = query(0, 1, new HashMap<String, Object>());
		if (all.size() > 0)
			return all.get(0);
		else
			return null;
	}

	public int queryCount(Map<String, Object> qryFilter) {
		sf.getCurrentSession().beginTransaction();
		try {
			Criteria c = sf.getCurrentSession()
					.createCriteria(getEntityClass());
			c.setProjection(Projections.rowCount());
			buildCriteria(c, qryFilter);
			Integer count = (Integer) c.uniqueResult();
			return count;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			sf.getCurrentSession().getTransaction().commit();
		}
		return 0;
	}

	public List<T> queryAll() {
		List<T> result = Collections.emptyList();
		sf.getCurrentSession().beginTransaction();
		try {
			Criteria c = sf.getCurrentSession()
					.createCriteria(getEntityClass());
			result = c.list();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			sf.getCurrentSession().getTransaction().commit();
		}
		return result;
	}

	public List<T> query(int firstRow, int rowsNumber,
			Map<String, Object> qryFilter) {
		List<T> result = Collections.emptyList();
		sf.getCurrentSession().beginTransaction();
		try {
			Criteria c = sf.getCurrentSession()
					.createCriteria(getEntityClass());
			buildCriteria(c, qryFilter);
			if (firstRow >= 0 && rowsNumber > 0) {
				c.setFirstResult(firstRow);
				c.setMaxResults(rowsNumber);
			}

			result = c.list();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			sf.getCurrentSession().getTransaction().commit();
		}
		return result;
	}

	protected Criteria buildCriteria(Criteria c, Map<String, Object> qryFilter) {
		doAddFilter(c, qryFilter);
		Order order=getOrderField();
		if (order != null) {
			c.addOrder(order);
		}
		return c;
	}

	protected final Criteria doAddFilter(Criteria c, Map<String, Object> qryFilter) {
		for (Map.Entry<String, Object> qryEntry : qryFilter.entrySet()) {
			String key = qryEntry.getKey();
			Object valueObj = qryEntry.getValue();

			if (valueObj instanceof Integer[]) {
				c.add(Restrictions.in(key, (Integer[]) valueObj));
			} else if (valueObj instanceof String[]) {
				c.add(Restrictions.in(key, (String[]) valueObj));
			} else {
				if(key.equalsIgnoreCase("id") && valueObj!=null){
					try{
						valueObj=Integer.parseInt(valueObj.toString());
						if(((Integer)valueObj)<0)
							continue;
					}catch(NumberFormatException ex){
						continue;
					}
				}
				Object value = evalueateProperty(key, valueObj);
				if(value!=null){
					c.add(Restrictions.eq(key, value));
				}
			}			
		}
		return c;
	}

	protected Order getOrderField() {
		return null;
	}

	protected abstract Class<T> getEntityClass();
	
	/**
	 * Override by subclass to get Foreign key Object Property instance.
	 * @param key
	 * @param valueObj
	 * @return
	 */
	protected Object evalueateProperty(String key, Object valueObj){
		return valueObj;
	}
}